package auth

import (
	"context"
	"errors"
	"fmt"
	"mpps/tools/config"
	"mpps/tools/repository/redis"
	"time"

	"github.com/asim/go-micro/v3/metadata"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

var (
	accessTokenSub  = "AccessToken"
	refreshTokenSub = "RefreshToken"
)

func ParseToken(tokenStr string, isRefresh bool) (jwt.MapClaims, error) {
	token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, nil
		}
		return []byte(config.JWTSecret), nil
	})
	if err != nil {
		return nil, errors.New("ErrAuth.Message")
	}
	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		id := claims["jti"]
		exp := claims["exp"]
		sub := claims["sub"]
		// 读取redis记录,新登入会挤掉之前登入AccessToken和RefreshToken
		switch sub {
		case accessTokenSub:
			getAccessEx := redis.GetRedis().GetEx(context.Background(), fmt.Sprintf("%v:%v", sub, id), time.Duration(exp.(float64)))
			if val, err := getAccessEx.Result(); err != nil || val != tokenStr {
				return nil, errors.New("ErrAuth.Message")
			}
		case refreshTokenSub:
			getRefreshEx := redis.GetRedis().GetEx(context.Background(), fmt.Sprintf("%v:%v", sub, id), time.Duration(exp.(float64)))
			if val, err := getRefreshEx.Result(); err != nil || val != tokenStr || !isRefresh {
				return nil, errors.New("ErrAuth.Message")
			}
		}
		return claims, nil
	}
	return nil, errors.New("ErrAuth.Message")
}

func GenerateToken(id string) (string, string, error) {
	now := time.Now().Unix()
	expAccess := time.Now().Unix() + int64(time.Minute*15)
	expRefresh := time.Now().Unix() + int64(time.Hour*24)
	accessToken, err := generateToken(id, now, expAccess, accessTokenSub)
	if err != nil {
		return "", "", err
	}
	refreshToken, err := generateToken(id, now, expRefresh, refreshTokenSub)
	if err != nil {
		return "", "", err
	}
	// 写入redis记录
	redis.GetRedis().SetEX(context.Background(), fmt.Sprintf("%v:%v", accessTokenSub, id), accessToken, time.Duration(expAccess))
	redis.GetRedis().SetEX(context.Background(), fmt.Sprintf("%v:%v", refreshTokenSub, id), refreshToken, time.Duration(expRefresh))
	return accessToken, refreshToken, nil
}

func UpdateToken(accessToken string, refreshToken string) (string, string, error) {
	// 忽略校验失效
	aToken, err := ignoreParseTokenExp(accessToken)
	if err != nil {
		return "", "", err
	}
	// 需要校验是否失效
	rToken, err := ParseToken(refreshToken, true)
	if err != nil {
		return "", "", err
	}
	aId := aToken["jti"]
	aSub := aToken["sub"]

	rId := rToken["jti"]
	rSub := rToken["sub"]
	rExp := rToken["exp"]

	if aId.(string) == rId.(string) && aSub.(string) == accessTokenSub && rSub.(string) == refreshTokenSub {
		// redis校验refreshToken
		getRefreshEx := redis.GetRedis().GetEx(context.Background(), fmt.Sprintf("%v:%v", rSub, rId), time.Duration(rExp.(float64)))
		if val, err := getRefreshEx.Result(); err != nil || val != refreshToken {
			return "", "", errors.New("ErrAuth.Message")
		}
		// 生成新token
		_accessToken, _refreshToken, err := GenerateToken(aId.(string))
		if err != nil {
			return "", "", err
		}
		return _accessToken, _refreshToken, nil
	}
	return "", "", errors.New("ErrAuth.Message")
}

func DeleteToken(accessToken string, refreshToken string) error {
	if aclaims, err := ignoreParseTokenExp(accessToken); err != nil {
		return err
	} else {
		aId := aclaims["jti"]
		aSub := aclaims["sub"]
		redis.GetRedis().Del(context.Background(), fmt.Sprintf("%v:%v", aSub, aId))
	}
	if rclaims, err := ignoreParseTokenExp(refreshToken); err != nil {
		return err
	} else {
		rId := rclaims["jti"]
		rSub := rclaims["sub"]
		redis.GetRedis().Del(context.Background(), fmt.Sprintf("%v:%v", rSub, rId))
	}
	return nil
}

func WithToken(c *gin.Context) context.Context {
	token := c.Request.Header.Get("token")
	ctx := metadata.NewContext(context.Background(), map[string]string{
		"Token": token, // 这里写成token,在rpc传输过程中micro框架会变成Token
	})
	return ctx
}

func generateToken(id string, now int64, exp int64, sub string) (string, error) {
	claims := make(jwt.MapClaims)
	claims["aud"] = config.WebSrv // 受众
	claims["exp"] = exp           // 失效时间
	claims["jti"] = id            // 编号
	claims["iat"] = now           // 签发时间
	claims["nbf"] = now           // 生效时间
	claims["iss"] = config.WebSrv // 签发人
	claims["sub"] = sub           // 主题
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenStr, err := token.SignedString([]byte(config.JWTSecret))
	if err != nil {
		return "", err
	}
	return tokenStr, nil
}

//忽略是否失效
func ignoreParseTokenExp(tokenStr string) (jwt.MapClaims, error) {
	token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, nil
		}
		return []byte(config.JWTSecret), nil
	})
	if err != nil {
		return nil, errors.New("ErrAuth.Message")
	}
	if claims, ok := token.Claims.(jwt.MapClaims); ok {
		return claims, nil
	}
	return nil, errors.New("ErrAuth.Message")
}
